package solver

import (
	"errors"
	"fmt"
	"os"
)

type Cell struct {
	value          int
	candidateCount int
	candidates     [9]bool
}

type House struct {
	solvedCount int
	cells       [9]*Cell
}

type Grid struct {
	solvedCount int
	cells       [9][9]Cell
	rows        [9]House
	cols        [9]House
	boxes       [9]House
	givens      [9][9]int
	strategies  []Strategy
	//strategyNames []string
}

func (g *Grid) CheckInput() error {
	if len(os.Args) == 1 {
		fmt.Println("You must provide a sudoku string.")
		return errors.New("input error")
	}

	if len(os.Args[1]) < 81 {
		fmt.Println("The sudoku string must be 81 characters long.")
		return errors.New("input error")
	}

	for _, value := range os.Args[1] {
		if value < '0' || value > '9' {
			fmt.Println("The sudokku string must only cantain 0 to 9 digits.")
			return errors.New("input error")
		}
	}

	return nil
}

func (g *Grid) Input(in string) {
	buff := [81]uint8{}
	pos := 0
	for _, c := range []byte(in) {
		if c >= '0' && c <= '9' {
			buff[pos] = c - '0'
			pos++
		}
		if c == '.' {
			pos++
		}
		if pos == 81 {
			break
		}
	}

	pos = 0
	for rowNum := 0; rowNum < 9; rowNum++ {
		for colNum := 0; colNum < 9; colNum++ {
			g.givens[rowNum][colNum] = int(buff[pos])
			pos++
			if g.givens[rowNum][colNum] != 0 {
				g.solvedCount++
			}
			g.cells[rowNum][colNum].set(g.givens[rowNum][colNum])
		}
	}
}

func (g *Grid) Init() {
	g.initRows()
	g.initCols()
	g.initBoxes()
	g.prepareStrategies()
}

func (g *Grid) initRows() {
	for rowNum := 0; rowNum < 9; rowNum++ {
		g.rows[rowNum].solvedCount = 0
		for colNum := 0; colNum < 9; colNum++ {
			g.rows[rowNum].cells[colNum] = &g.cells[rowNum][colNum]
			if g.cells[rowNum][colNum].value != 0 {
				g.rows[rowNum].solvedCount += 1
			}
		}
	}
}

func (g *Grid) initCols() {
	for colNum := 0; colNum < 9; colNum++ {
		g.cols[colNum].solvedCount = 0
		for rowNum := 0; rowNum < 9; rowNum++ {
			g.cols[colNum].cells[rowNum] = &g.cells[rowNum][colNum]
			if g.cells[rowNum][colNum].value != 0 {
				g.cols[colNum].solvedCount += 1
			}
		}
	}
}

func (g *Grid) initBoxes() {
	for boxNum := 0; boxNum < 9; boxNum++ {
		g.boxes[boxNum].solvedCount = 0
		for cellNum := 0; cellNum < 9; cellNum++ {
			rowNum, colNum := calcRowAndColNum(boxNum, cellNum)
			g.boxes[boxNum].cells[cellNum] = &g.cells[rowNum][colNum]
			if g.cells[rowNum][colNum].value != 0 {
				g.boxes[boxNum].solvedCount += 1
			}
		}
	}
}

func printBoldBorder() {
	fmt.Println("++===+===+===++===+===+===++===+===+===++")
}

func printFineBorder() {
	fmt.Println("++---+---+---++---+---+---++---+---+---++")
}

func (g *Grid) printLineOfRow(row House, lineNum int) {
	fmt.Print("||")
	for colNum, cell := range row.cells {
		if cell.value != 0 {
			if lineNum == 1 {
				fmt.Printf("(%d)", cell.value)
			} else {
				fmt.Print("   ")
			}
		} else {
			candNum := lineNum * 3
			for i, candidate := range cell.candidates[candNum : candNum+3] {
				if candidate {
					candValue := candNum + i + 1
					fmt.Print(candValue)
				} else {
					fmt.Print(" ")
				}
			}
		}
		if (colNum+1)%3 == 0 {
			fmt.Print("||")
		} else {
			fmt.Print("|")
		}
	}
	fmt.Println()
}

func (g *Grid) printRow(row House) {
	g.printLineOfRow(row, 0)
	g.printLineOfRow(row, 1)
	g.printLineOfRow(row, 2)
}

func (g *Grid) PrintGrid() {
	printBoldBorder()
	for rowNum, row := range g.rows {
		g.printRow(row)
		if (rowNum+1)%3 == 0 {
			printBoldBorder()
		} else {
			printFineBorder()
		}
	}
}
